
#pragma once

#include "BVH.hpp"
#include "Intersection.hpp"
#include "Material.hpp"
#include "Object.hpp"
#include "device/Vector_dev.hpp"
#include "host_utils.hpp"

__device__ static bool rayTriangleIntersect(const Vector3fDev &v0, const Vector3fDev &v1,
                                            const Vector3fDev &v2, const Vector3fDev &orig,
                                            const Vector3fDev &dir, float &tnear, float &u, float &v)
{
    Vector3fDev edge1 = v1 - v0;
    Vector3fDev edge2 = v2 - v0;
    Vector3fDev pvec = crossProduct(dir, edge2);
    float det = dotProduct(edge1, pvec);
    if (det == 0 || det < 0)
        return false;

    Vector3fDev tvec = orig - v0;
    u = dotProduct(tvec, pvec);
    if (u < 0 || u > det)
        return false;

    Vector3fDev qvec = crossProduct(tvec, edge1);
    v = dotProduct(dir, qvec);
    if (v < 0 || u + v > det)
        return false;

    float invDet = 1 / det;

    tnear = dotProduct(edge2, qvec) * invDet;
    u *= invDet;
    v *= invDet;

    return true;
}

class Triangle : public Object
{
public:
    Vector3fDev v0, v1, v2; // vertices A, B ,C , counter-clockwise order
    Vector3fDev e1, e2;     // 2 edges v1-v0, v2-v0;
    Vector3fDev t0, t1, t2; // texture coords
    Vector3fDev normal;
    float area;
    Material *m;
    float EPSILON = 0.00001F;

    Triangle(Vector3fDev _v0, Vector3fDev _v1, Vector3fDev _v2, Material *_m)
        : Object(2), v0(_v0), v1(_v1), v2(_v2), m(_m)
    {
        e1 = v1.host_sub(v0);
        e2 = v2.host_sub(v0);
        normal = normalize(crossProduct(e1, e2, true), true);
        area = crossProduct(e1, e2, true).norm(true) * 0.5f;
    }

    __device__ bool intersect(const Ray &ray);
    __device__ bool intersect(const Ray &ray, float &tnear,
                              uint32_t &index) const;
    __device__ Intersection getIntersection(Ray ray)
    {
        Intersection inter;

        if (dotProduct(ray.direction, normal) > 0)
            return inter;
        double u, v, t_tmp = 0;
        Vector3fDev pvec = crossProduct(ray.direction, e2);
        double det = dotProduct(e1, pvec);
        if (fabs(det) < EPSILON)
            return inter;

        double det_inv = 1. / det;
        Vector3fDev tvec = ray.origin - v0;
        u = dotProduct(tvec, pvec) * det_inv;
        if (u < 0 || u > 1)
            return inter;
        Vector3fDev qvec = crossProduct(tvec, e1);
        v = dotProduct(ray.direction, qvec) * det_inv;
        if (v < 0 || u + v > 1)
            return inter;
        t_tmp = dotProduct(e2, qvec) * det_inv;

        // TODO find ray triangle intersection
        if (t_tmp > 0.0)
        {
            // tnear = t;
            // u = b1;
            // v = b2;
            inter.distance = t_tmp;
            inter.m = m;
            inter.obj = this;
            inter.normal = normal;
            // inter.coords = ray(t_tmp);
            inter.coords = ray.origin + ray.direction * t_tmp;
            inter.happened = true;
        }
        else
        {
            inter.happened = false;
        }

        return inter;
    }

    __device__ void getSurfaceProperties(const Vector3fDev &P, const Vector3fDev &I,
                                         const uint32_t &index, const Vector2fDev &uv,
                                         Vector3fDev &N, Vector2fDev &st) const
    {
        N = normal;
        //        throw std::runtime_error("triangle::getSurfaceProperties not
        //        implemented.");
    }
    __device__ Vector3fDev evalDiffuseColor(const Vector2fDev &) const;
    __device__ Bounds3 getBounds();
    Bounds3 getBounds(bool isFromHost);

    __device__ void Sample(Intersection &pos, float &pdf, int thread_id)
    {
        float x = sqrt(get_random_float(thread_id)), y = get_random_float(thread_id * 2);
        pos.coords = v0 * (1.0f - x) + v1 * (x * (1.0f - y)) + v2 * (x * y);
        pos.normal = this->normal;
        pdf = 1.0f / area;
    }

    __device__ float getArea()
    {
        return area;
    }

    __device__ bool hasEmit()
    {
        return m->hasEmission();
    }
};

class MeshTriangle : public Object
{
public:
    MeshTriangle(const std::string &filename, Material *mt = new Material());

    __device__ bool intersect(const Ray &ray) { return true; }

    __device__ bool intersect(const Ray &ray, float &tnear, uint32_t &index) const
    {
        bool intersect = false;
        for (uint32_t k = 0; k < numTriangles; ++k)
        {
            const Vector3fDev &v0 = vertices[vertexIndex[k * 3]];
            const Vector3fDev &v1 = vertices[vertexIndex[k * 3 + 1]];
            const Vector3fDev &v2 = vertices[vertexIndex[k * 3 + 2]];
            float t, u, v;
            if (rayTriangleIntersect(v0, v1, v2, ray.origin, ray.direction, t,
                                     u, v) &&
                t < tnear)
            {
                tnear = t;
                index = k;
                intersect |= true;
            }
        }

        return intersect;
    }

    __device__ Bounds3 getBounds()
    {
        return bounding_box;
    }

    Bounds3 getBounds(bool isFromHost)
    {
        assert(isFromHost);
        return bounding_box;
    }

    __device__ void getSurfaceProperties(const Vector3fDev &P, const Vector3fDev &I,
                                         const uint32_t &index, const Vector2fDev &uv,
                                         Vector3fDev &N, Vector2fDev &st) const
    {
        const Vector3fDev &v0 = vertices[vertexIndex[index * 3]];
        const Vector3fDev &v1 = vertices[vertexIndex[index * 3 + 1]];
        const Vector3fDev &v2 = vertices[vertexIndex[index * 3 + 2]];
        Vector3fDev e0 = normalize(v1 - v0);
        Vector3fDev e1 = normalize(v2 - v1);
        N = normalize(crossProduct(e0, e1));
        const Vector2fDev &st0 = stCoordinates[vertexIndex[index * 3]];
        const Vector2fDev &st1 = stCoordinates[vertexIndex[index * 3 + 1]];
        const Vector2fDev &st2 = stCoordinates[vertexIndex[index * 3 + 2]];
        st = st0 * (1 - uv.x - uv.y) + st1 * uv.x + st2 * uv.y;
    }

    __device__ Vector3fDev evalDiffuseColor(const Vector2fDev &st) const
    {
        float scale = 5;
        float pattern =
            (fmodf(st.x * scale, 1) > 0.5) ^ (fmodf(st.y * scale, 1) > 0.5);
        // return lerp(Vector3fDev(0.815, 0.235, 0.031),
        //             Vector3fDev(0.937, 0.937, 0.231), pattern);
        return Vector3fDev(0.815, 0.235, 0.031);
    }

    __device__ Intersection getIntersection(Ray ray)
    {
        Intersection intersec;
        if (bvh)
        {
            intersec = bvh->Intersect(ray);
        }

        return intersec;
    }

    __device__ void Sample(Intersection &pos, float &pdf, int thread_id)
    {
        bvh->Sample(pos, pdf, thread_id);
        pos.emit = m->getEmission();
    }

    __device__ float getArea()
    {
        return area;
    }
    __device__ bool hasEmit()
    {
        return m->hasEmission();
    }

    Bounds3 bounding_box;
    std::unique_ptr<Vector3fDev[]> vertices;
    uint32_t numTriangles;
    std::unique_ptr<uint32_t[]> vertexIndex;
    std::unique_ptr<Vector2fDev[]> stCoordinates;

    std::vector<Triangle *> triangles;

    BVHAccel *bvh;
    float area;

    Material *m;
};
